{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "eIrvnAbGZ1wP"
      },
      "source": [
        "##### Copyright 2019 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "_A4IPZ-WZ9H7"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "zfiHTzhkmNwd"
      },
      "source": [
        "# Рекуррентные нейронные сети (RNN) с Keras\n",
        "\n",
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://www.tensorflow.org/guide/keras/rnn\">\n",
        "    <img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" />\n",
        "    Смотрите на TensorFlow.org</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ru/guide/keras/rnn.ipynb\">\n",
        "    <img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />\n",
        "    Запустите в Google Colab</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ru/guide/keras/rnn.ipynb\">\n",
        "    <img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />\n",
        "    Изучайте код на GitHub</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ru/guide/keras/rnn.ipynb\">\n",
        "    <img src=\"https://www.tensorflow.org/images/download_logo_32px.png\" />\n",
        "    Скачайте ноутбук</a>\n",
        "  </td>\n",
        "</table>\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jfOdaQLhXLDR"
      },
      "source": [
        "Рекуррентные нейронные сети (RNN) - это класс нейронных сетей, которые хороши для моделирования последовательных данных, таких как временные ряды или естественный язык.\n",
        "\n",
        "Если схематично, слой RNN использует цикл `for` для итерации по упорядоченной по времени последовательности, храня при этом во внутреннем состоянии, закодированную информацию о шагах, которые он уже видел.\n",
        "\n",
        "Keras RNN API разработан с фокусом на:\n",
        "\n",
        "- **Простоту использования**: встроенные слои `tf.keras.layers.RNN`, `tf.keras.layers.LSTM`, `tf.keras.layers.GRU` позволяют вам быстро построить рекуррентные модели без необходимости делать сложные конфигурационные настройки.\n",
        "  \n",
        "- **Простоту кастомизации**: Вы можете также задать собственный слой ячеек RNN (внутреннюю часть цикла `for`) с кастомным поведением и использовать его с общим слоем `tf.keras.layers.RNN` (сам цикл `for`). Это позволит вам быстро прототипировать различные исследовательские идеи в гибкой манере, с минимумом кода.\n",
        "  "
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QGJH5EKYoSHZ"
      },
      "source": [
        "## Установка"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "wJEBe8hTlB6W"
      },
      "outputs": [],
      "source": [
        "from __future__ import absolute_import, division, print_function, unicode_literals\n",
        "\n",
        "import collections\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "\n",
        "try:\n",
        "  # %tensorflow_version only exists in Colab.\n",
        "  %tensorflow_version 2.x\n",
        "except Exception:\n",
        "  pass\n",
        "import tensorflow as tf\n",
        "\n",
        "from tensorflow.keras import layers"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DznzjxWCilt4"
      },
      "source": [
        "## Построение простой модели\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "H5tPG7KJirBj"
      },
      "source": [
        "В Keras есть три встроенных слоя RNN:\n",
        "\n",
        "1. `tf.keras.layers.SimpleRNN`, полносвязная RNN в которой выход предыдущего временного шага должен быть передан в следующий шаг.\n",
        "\n",
        "2. `tf.keras.layers.GRU`, впервые предложено в [Изучение представлений фраз с использованием кодера-декодера RNN для статистического машинного перевода](https://arxiv.org/abs/1406.1078).\n",
        "\n",
        "3. `tf.keras.layers.LSTM`, впервые предложено в [Долгая краткосрочная память](https://www.bioinf.jku.at/publications/older/2604.pdf).\n",
        "\n",
        "В начале 2015, у Keras первые появились переиспользуемые реализации LSTM и GRU на Python с открытым исходным кодом.\n",
        "\n",
        "Ниже пример `Sequential` модели которая обрабатывает последовательности целых чисел, вкладывая каждое целое число в 64-мерный вектор, затем обрабатывая последовательности векторов с использованием слоя `LSTM`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "QHdAFEATnFpn"
      },
      "outputs": [],
      "source": [
        "model = tf.keras.Sequential()\n",
        "# Добавим слой Embedding ожидая на входе словарь размера 1000,\n",
        "# и на выходе вложение размерностью 64.\n",
        "model.add(layers.Embedding(input_dim=1000, output_dim=64))\n",
        "\n",
        "# Добавим слой LSTM с 128 внутренними узлами.\n",
        "model.add(layers.LSTM(128))\n",
        "\n",
        "# Добавим слой Dense с 10 узлами и активацией softmax.\n",
        "model.add(layers.Dense(10, activation='softmax'))\n",
        "\n",
        "model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "sVT4R7O3qDXM"
      },
      "source": [
        "## Выходы и состояния"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "IOQnPR9eqLwk"
      },
      "source": [
        "По умолчанию выход слоя RNN содержит один вектор на элемент. Этот вектор является выходом последней ячейки RNN, содержащей информацию обо всей входной последовательности. Размерность этого выхода `(batch_size, units)`, где `units` соответствует аргументу `units` передаваемому конструктору слоя.\n",
        "\n",
        "Слой RNN может также возвращать всю последовательность выходных данных для каждого элемента (по одному вектору на каждый шаг), если вы укажете `return_sequences=True`. Размерность этих выходных данных равна `(batch_size, timesteps, units)`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "wNlkR8oXpNEx"
      },
      "outputs": [],
      "source": [
        "model = tf.keras.Sequential()\n",
        "model.add(layers.Embedding(input_dim=1000, output_dim=64))\n",
        "\n",
        "# Выходом GRU будет 3D тензор размера (batch_size, timesteps, 256)\n",
        "model.add(layers.GRU(256, return_sequences=True))\n",
        "\n",
        "# Выходом SimpleRNN будет 2D тензор размера (batch_size, 128)\n",
        "model.add(layers.SimpleRNN(128))\n",
        "\n",
        "model.add(layers.Dense(10, activation='softmax'))\n",
        "\n",
        "model.summary() "
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "1HagyjYos5rD"
      },
      "source": [
        "Кроме того, слой RNN может вернуть свое конечное внутреннее состояние(состояния). Возвращенные состояния можно использовать позже для возобновления выполнения RNN или [для инициализации другой RNN](https://arxiv.org/abs/1409.3215). Эта настройка обычно используется в модели энкодер-декодер последовательность к последовательности, где итоговое состояние энкодера используется для начального состояния декодера.\n",
        "\n",
        "Для того чтобы слой RNN возвращал свое внутреннего состояния, установите параметр 'return_state' в значение 'True' при создании слоя. Обратите внимание, что у `LSTM` 2 тензора состояния, а у `GRU` только один.\n",
        "\n",
        "Чтобы настроить начальное состояние слоя, просто вызовите слой с дополнительным аргументом `initial_state`.\n",
        "Заметьте что размерность должна совпадать с размерностью элемента слоя, как в следующем примере."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "2_HsGBrDvaea"
      },
      "outputs": [],
      "source": [
        "encoder_vocab = 1000\n",
        "decoder_vocab = 2000\n",
        "\n",
        "encoder_input = layers.Input(shape=(None, ))\n",
        "encoder_embedded = layers.Embedding(input_dim=encoder_vocab, output_dim=64)(encoder_input)\n",
        "\n",
        "# Возвращает состояния в добавление к выходным данным\n",
        "output, state_h, state_c = layers.LSTM(\n",
        "    64, return_state=True, name='encoder')(encoder_embedded)\n",
        "encoder_state = [state_h, state_c]\n",
        "\n",
        "decoder_input = layers.Input(shape=(None, ))\n",
        "decoder_embedded = layers.Embedding(input_dim=decoder_vocab, output_dim=64)(decoder_input)\n",
        "\n",
        "# Передает 2 состояния в новый слой LSTM в качестве начального состояния\n",
        "decoder_output = layers.LSTM(\n",
        "    64, name='decoder')(decoder_embedded, initial_state=encoder_state)\n",
        "output = layers.Dense(10, activation='softmax')(decoder_output)\n",
        "\n",
        "model = tf.keras.Model([encoder_input, decoder_input], output)\n",
        "model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "kJDJSXjZ2VaY"
      },
      "source": [
        "## RNN слои и RNN ячейки"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hQRxLRSS2gDf"
      },
      "source": [
        "RNN API в дополнение к встроенным слоям RNN, также предоставляет API на уровне ячейки. В отличие от слоев RNN, которые обрабатывают целые пакеты входных последовательностей, ячейка RNN обрабатывает только один временной шаг.\n",
        "\n",
        "Ячейка находится внутри цикла `for` слоя RNN . Оборачивание ячейки слоем `tf.keras.layers.RNN` дает вам слой способный обрабатывать пакеты последовательностей, напр. `RNN(LSTMCell(10))`.\n",
        "\n",
        "Математически, `RNN(LSTMCell(10))` дает тот же результат, что и `LSTM(10)`. Фактически, реализацией этого слоя внутри TF v1.x было лишь создание соответствующей RNN ячейки и оборачивание ее в слой RNN.  Однако использование встроенных слоев `GRU` и `LSTM` позволяет использовать CuDNN что может дать лучшую производительность.\n",
        "\n",
        "Существует три встроенных ячейки RNN, каждая из которых соответствует своему слою RNN.\n",
        "\n",
        "- `tf.keras.layers.SimpleRNNCell` соответствует слою `SimpleRNN`.\n",
        "\n",
        "- `tf.keras.layers.GRUCell` соответствует слою  `GRU`.\n",
        "\n",
        "- `tf.keras.layers.LSTMCell` соответствует слою `LSTM`.\n",
        "\n",
        "Абстракция ячейки вместе с общим классом `tf.keras.layers.RNN`, позволяет очень легко реализовать кастомные RNN архитектуры для ваших исследований.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "veiCKSUU-ina"
      },
      "source": [
        "## Кросс-пакетное сохранение состояния"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "EvAaiMJbWR2A"
      },
      "source": [
        "При обработке длинных последовательностей (возможно бесконечных), вы можете захотеть использовать паттерн **кросс-пакетное сохранение состояния (cross-batch statefulness)**.\n",
        "\n",
        "Обычно, внутреннее состояние слоя RNN сбрасывается при каждом новом пакете данных (т.е. каждый пример который видит слой предполагается независимым от прошлого). Слой будет поддерживать состояние только на время обработки данного элемента.\n",
        "\n",
        "Однако, если у вас очень длинные последовательности, полезно разбить их на более короткие и по очереди передавать их в слой RNN без сброса состояния слоя. Таким образом, слой может сохранять информацию обо всей последовательности, хотя он будет видеть только одну подпоследовательность за раз.\n",
        "\n",
        "Вы можете сделать это установив в конструкторе `stateful=True`.\n",
        "\n",
        "Если у вас есть последовательность `s = [t0, t1, ... t1546, t1547]`, вы можете разбить ее например на:\n",
        "\n",
        "```\n",
        "s1 = [t0, t1, ... t100]\n",
        "s2 = [t101, ... t201]\n",
        "...\n",
        "s16 = [t1501, ... t1547]\n",
        "```\n",
        "\n",
        "Потом вы можете обработать ее с помощью:\n",
        "\n",
        "```python\n",
        "lstm_layer = layers.LSTM(64, stateful=True)\n",
        "for s in sub_sequences:\n",
        "  output = lstm_layer(s)\n",
        "```\n",
        "\n",
        "Когда вы захотите почистить состояние, используйте `layer.reset_states()`.\n",
        "\n",
        "\n",
        "> Примечание: В этом случае, предполагается что пример `i` в данном пакете является продолжением примера `i` предыдущего пакета. Это значит, что все пакеты содержат одинаковое количество элементов (размер пакета). Например, если пакет содержит `[sequence_A_from_t0_to_t100,  sequence_B_from_t0_to_t100]`, следующий пакет должен содержать `[sequence_A_from_t101_to_t200,  sequence_B_from_t101_to_t200]`.\n",
        "\n",
        "\n",
        "\n",
        "\n",
        "Вот полный пример:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "E6TsLXJ0X3Xd"
      },
      "outputs": [],
      "source": [
        "paragraph1 = np.random.random((20, 10, 50)).astype(np.float32)\n",
        "paragraph2 = np.random.random((20, 10, 50)).astype(np.float32)\n",
        "paragraph3 = np.random.random((20, 10, 50)).astype(np.float32)\n",
        "\n",
        "lstm_layer = layers.LSTM(64, stateful=True)\n",
        "output = lstm_layer(paragraph1)\n",
        "output = lstm_layer(paragraph2)\n",
        "output = lstm_layer(paragraph3)\n",
        "\n",
        "# reset_states() сбосит кешированное состояние до изначального initial_state.\n",
        "# Если initial_state не было задано, по умолчанию будут использованы нулевые состояния.\n",
        "lstm_layer.reset_states()\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7AtPur5BDzb4"
      },
      "source": [
        "## Двунаправленные RNN"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "OsdEIXXREL_N"
      },
      "source": [
        "Для последовательностей отличных от временных рядов (напр. текстов), часто бывает так, что модель RNN работает лучше, если она обрабатывает последовательность не только от начала до конца, но и наоборот. Например, чтобы предсказать следующее слово в предложении, часто полезно знать контекст вокруг слова, а не только слова идущие перед ним.\n",
        "\n",
        "Keras предоставляет простой API для создания таких двунаправленных сетей RNN: обертку `tf.keras.layers.Bidirectional`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "MNhYIAXqYl3B"
      },
      "outputs": [],
      "source": [
        "model = tf.keras.Sequential()\n",
        "\n",
        "model.add(layers.Bidirectional(layers.LSTM(64, return_sequences=True), \n",
        "                               input_shape=(5, 10)))\n",
        "model.add(layers.Bidirectional(layers.LSTM(32)))\n",
        "model.add(layers.Dense(10, activation='softmax'))\n",
        "\n",
        "model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ThwlodTjZCU0"
      },
      "source": [
        "Под капотом, `Bidirectional` скопирует переданный слой RNN layer, и перевернет поле `go_backwards` вновь скопированного слоя, и таким образом входные данные будут обработаны в обратном порядке.\n",
        "\n",
        "На выходе `Bidirectional` RNN по умолчанию будет сумма вывода прямого слоя и вывода обратного слоя. Если вам нужно другое поведение слияния, напр. конкатенация, поменяйте параметр `merge_mode` в конструкторе обертки `Bidirectional`. Чтобы узнать больше о `Bidirectional`, посмотрите [документацию API](https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/keras/layers/Bidirectional)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ANGN956w6FRs"
      },
      "source": [
        "## Оптимизация производительности и ядра CuDNN в TensorFlow 2.0"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "76xAs7epaX21"
      },
      "source": [
        "В TensorFlow 2.0, встроенные слои LSTM и GRU пригодны для использования ядер CuDNN по умолчанию, если доступен графический процессор. С этим изменением предыдущие слои `keras.layers.CuDNNLSTM/CuDNNGRU` устарели, и вы можете построить свою модель, не беспокоясь об оборудовании, на котором она будет работать.\n",
        "\n",
        "Поскольку ядро CuDNN построено с некоторыми допущениями, это значит, что слой **не сможет использовать слой CuDNN kernel если вы измените параметры по умолчанию встроенных слоев LSTM или GRU**. Напр:\n",
        "\n",
        "- Изменение функции `activation` с `tanh` на что-то другое.\n",
        "- Изменение функции `recurrent_activation` с `sigmoid` на что-то другое.\n",
        "- Использование `recurrent_dropout` > 0.\n",
        "- Установка `unroll` равным True, что заставляет LSTM/GRU декомпозировать внутренний `tf.while_loop` в развернутый цикл `for`.\n",
        "- Установка `use_bias` равным False.\n",
        "- Использование масок, когда входные данные не выровнены строго справа (если маска соответствует строго выровненным справа данным, CuDNN может быть все еще использовано. Это наиболее распространенный случай).\n",
        "\n",
        "Для детального списка ограничений пожалуйста см. документацию для слоев [LSTM](https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/keras/layers/LSTM) и [GRU](https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/keras/layers/GRU)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ybd73JmvqLp4"
      },
      "source": [
        "### Когда это возможно используйте ядра CuDNN\n",
        "\n",
        "Давайте построим простую модель LSTM чтобы показать разницу в производительности.\n",
        "\n",
        "Мы будем использовать в качестве входных данных последовательности строк цифр MNIST (обрабатывая каждую строку пикселей как один временной шаг), а прогнозировать будем метку цифры.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "m9kM9hwRsxMx"
      },
      "outputs": [],
      "source": [
        "batch_size = 64\n",
        "# Каждый пакет изображений MNIST это тензор размерностью (batch_size, 28, 28).\n",
        "# Каждая входная последовательность размера (28, 28) (высота рассматривается как время).\n",
        "input_dim = 28\n",
        "\n",
        "units = 64\n",
        "output_size = 10  # метки от 0 до 9\n",
        "\n",
        "# Построим RNN модель\n",
        "def build_model(allow_cudnn_kernel=True):\n",
        "  # CuDNN доступен только на уровне слоя, а не на уровне ячейки.\n",
        "  # Это значит `LSTM(units)` будет использовать ядро CuDNN,\n",
        "  # тогда как RNN(LSTMCell(units)) будет использовать non-CuDNN ядро.\n",
        "  if allow_cudnn_kernel:\n",
        "    # Слой LSTM с параметрами по умолчанию использует CuDNN.\n",
        "    lstm_layer = tf.keras.layers.LSTM(units, input_shape=(None, input_dim))\n",
        "  else:\n",
        "    # Обертка LSTMCell слоем RNN не будет использовать CuDNN.\n",
        "    lstm_layer = tf.keras.layers.RNN(\n",
        "        tf.keras.layers.LSTMCell(units),\n",
        "        input_shape=(None, input_dim))\n",
        "  model = tf.keras.models.Sequential([\n",
        "      lstm_layer,\n",
        "      tf.keras.layers.BatchNormalization(),\n",
        "      tf.keras.layers.Dense(output_size, activation='softmax')]\n",
        "  )\n",
        "  return model\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "uuztNezFh0BL"
      },
      "source": [
        "### Загрузка датасета MNIST"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "m_kZTLDobchi"
      },
      "outputs": [],
      "source": [
        "mnist = tf.keras.datasets.mnist\n",
        "\n",
        "(x_train, y_train), (x_test, y_test) = mnist.load_data()\n",
        "x_train, x_test = x_train / 255.0, x_test / 255.0\n",
        "sample, sample_label = x_train[0], y_train[0]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UXF8elCuib8k"
      },
      "source": [
        "### Создайте экземпляр модели и скомпилируйте его\n",
        "Мы выбрали `sparse_categorical_crossentropy` в качестве функции потерь. Выходные данные модели имеют размерность `[batch_size, 10]`. Ответом модели является целочисленный вектор, каждое из чисел находится в диапазоне от 0 до 9."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "klgv6dfK0KNb"
      },
      "outputs": [],
      "source": [
        "model = build_model(allow_cudnn_kernel=True)\n",
        "\n",
        "model.compile(loss='sparse_categorical_crossentropy', \n",
        "              optimizer='sgd',\n",
        "              metrics=['accuracy'])\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "qzeeo65r25CU"
      },
      "outputs": [],
      "source": [
        "model.fit(x_train, y_train,\n",
        "          validation_data=(x_test, y_test),\n",
        "          batch_size=batch_size,\n",
        "          epochs=5)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "kvCWAssZjsdW"
      },
      "source": [
        "### Построим новую модель без ядра CuDNN"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "H2JfHDOhOFtx"
      },
      "outputs": [],
      "source": [
        "slow_model = build_model(allow_cudnn_kernel=False)\n",
        "slow_model.set_weights(model.get_weights())\n",
        "slow_model.compile(loss='sparse_categorical_crossentropy', \n",
        "                   optimizer='sgd', \n",
        "                   metrics=['accuracy'])\n",
        "slow_model.fit(x_train, y_train, \n",
        "               validation_data=(x_test, y_test), \n",
        "               batch_size=batch_size,\n",
        "               epochs=5)  # Обучим только за одну эпоху потому что она медленная."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Zx8QLf81dTVr"
      },
      "source": [
        "Как вы можете видеть, модель построенная с CuDNN намного быстрее для обучения чем модель использующая обычное ядро TensorFlow.\n",
        "\n",
        "Ту же модель с поддержкой CuDNN можно использовать при выводе в однопроцессорной среде. Аннотация `tf.device` просто указывает используемое устройство. Модель выполнися по умолчанию на CPU если не будет доступно GPU.\n",
        "\n",
        "Вам просто не нужно беспокоиться о железе на котором вы работаете. Разве это не круто?"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "z_z1eRh1fMBL"
      },
      "outputs": [],
      "source": [
        "with tf.device('CPU:0'):\n",
        "  cpu_model = build_model(allow_cudnn_kernel=True)\n",
        "  cpu_model.set_weights(model.get_weights())\n",
        "  result = tf.argmax(cpu_model.predict_on_batch(tf.expand_dims(sample, 0)), axis=1)\n",
        "  print('Предсказанный результат: %s, реальный результат: %s' % (result.numpy(), sample_label))\n",
        "  plt.imshow(sample, cmap=plt.get_cmap('gray'))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2mCetBoTiqcB"
      },
      "source": [
        "## RNN с входными данными вида список/словарь, или вложенными входными данными\n",
        "\n",
        "Вложенные структуры позволяют включать больше информации в один временной шаг. Например, видеофрейм может содержать аудио и видео на входе одновременно. Размерность данных в этом случае может быть:\n",
        "\n",
        "`[batch, timestep, {\"video\": [height, width, channel], \"audio\": [frequency]}]`\n",
        "\n",
        "В другом примере, у рукописных данных могут быть обе координаты x и y для текущей позиции ручки, так же как и информация о давлении. Так что данные могут быть представлены так:\n",
        "\n",
        "`[batch, timestep, {\"location\": [x, y], \"pressure\": [force]}]`\n",
        "\n",
        "В следующем коде построен пример кастомной ячейки RNN которая работает с такими структурированными входными данными.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "A1IkIxWykSZQ"
      },
      "source": [
        "### Определите пользовательскую ячейку поддерживающую вложенный вход/выход"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "6yOT8nSqzp4A"
      },
      "outputs": [],
      "source": [
        "NestedInput = collections.namedtuple('NestedInput', ['feature1', 'feature2'])\n",
        "NestedState = collections.namedtuple('NestedState', ['state1', 'state2'])\n",
        "\n",
        "class NestedCell(tf.keras.layers.Layer):\n",
        "\n",
        "  def __init__(self, unit_1, unit_2, unit_3, **kwargs):\n",
        "    self.unit_1 = unit_1\n",
        "    self.unit_2 = unit_2\n",
        "    self.unit_3 = unit_3\n",
        "    self.state_size = NestedState(state1=unit_1, \n",
        "                                  state2=tf.TensorShape([unit_2, unit_3]))\n",
        "    self.output_size = (unit_1, tf.TensorShape([unit_2, unit_3]))\n",
        "    super(NestedCell, self).__init__(**kwargs)\n",
        "\n",
        "  def build(self, input_shapes):\n",
        "    # ожидает input_shape содержащий 2 элемента, [(batch, i1), (batch, i2, i3)]\n",
        "    input_1 = input_shapes.feature1[1]\n",
        "    input_2, input_3 = input_shapes.feature2[1:]\n",
        "\n",
        "    self.kernel_1 = self.add_weight(\n",
        "        shape=(input_1, self.unit_1), initializer='uniform', name='kernel_1')\n",
        "    self.kernel_2_3 = self.add_weight(\n",
        "        shape=(input_2, input_3, self.unit_2, self.unit_3),\n",
        "        initializer='uniform',\n",
        "        name='kernel_2_3')\n",
        "\n",
        "  def call(self, inputs, states):\n",
        "    # входы должны быть в [(batch, input_1), (batch, input_2, input_3)]\n",
        "    # состояние должно быть размерностью [(batch, unit_1), (batch, unit_2, unit_3)]\n",
        "    input_1, input_2 = tf.nest.flatten(inputs)\n",
        "    s1, s2 = states\n",
        "\n",
        "    output_1 = tf.matmul(input_1, self.kernel_1)\n",
        "    output_2_3 = tf.einsum('bij,ijkl->bkl', input_2, self.kernel_2_3)\n",
        "    state_1 = s1 + output_1\n",
        "    state_2_3 = s2 + output_2_3\n",
        "\n",
        "    output = [output_1, output_2_3]\n",
        "    new_states = NestedState(state1=state_1, state2=state_2_3)\n",
        "\n",
        "    return output, new_states"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "BJHOrrybk6Zy"
      },
      "source": [
        "### Постройте модель RNN с вложенными входом/выходом\n",
        "\n",
        "Давайте построим модель Keras которая использует слой `tf.keras.layers.RNN` и кастомную ячейку которую мы только что определили."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "itrDe0Y2qPjP"
      },
      "outputs": [],
      "source": [
        "unit_1 = 10\n",
        "unit_2 = 20\n",
        "unit_3 = 30\n",
        "\n",
        "input_1 = 32\n",
        "input_2 = 64\n",
        "input_3 = 32\n",
        "batch_size = 64\n",
        "num_batch = 100\n",
        "timestep = 50\n",
        "\n",
        "cell = NestedCell(unit_1, unit_2, unit_3)\n",
        "rnn = tf.keras.layers.RNN(cell)\n",
        "\n",
        "inp_1 = tf.keras.Input((None, input_1))\n",
        "inp_2 = tf.keras.Input((None, input_2, input_3))\n",
        "\n",
        "outputs = rnn(NestedInput(feature1=inp_1, feature2=inp_2))\n",
        "\n",
        "model = tf.keras.models.Model([inp_1, inp_2], outputs)\n",
        "\n",
        "model.compile(optimizer='adam', loss='mse', metrics=['accuracy'])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2MaihTM2mDcp"
      },
      "source": [
        "### Обучите модель на случайно сгенерированных данных\n",
        "\n",
        "Поскольку у нас нет хорошего датасета для этой модели, мы используем для демонстрации случайные Numpy данные."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "lN-imRqElz2S"
      },
      "outputs": [],
      "source": [
        "input_1_data = np.random.random((batch_size * num_batch, timestep, input_1))\n",
        "input_2_data = np.random.random((batch_size * num_batch, timestep, input_2, input_3))\n",
        "target_1_data = np.random.random((batch_size * num_batch, unit_1))\n",
        "target_2_data = np.random.random((batch_size * num_batch, unit_2, unit_3))\n",
        "input_data = [input_1_data, input_2_data]\n",
        "target_data = [target_1_data, target_2_data]\n",
        "\n",
        "model.fit(input_data, target_data, batch_size=batch_size)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "oDdrwgBWnjYp"
      },
      "source": [
        "Со слоем `tf.keras.layers.RNN` из Keras, от вас требуется только определить математическую логику отдельного шага внутри последовательности, а слой `tf.keras.layers.RNN` будет обрабатывать для вас итерацию последовательности. Это невероятно сильный способ быстрого прототипирования новых видов RNN (напр. вариант LSTM).\n",
        "\n",
        "Для более подробной информации посетите [документы API](https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/keras/layers/RNN)."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "rnn.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
